'''
Game of Life 
Date 2019/4/8
Version 0.3
'''
import time
import numpy as np
import matplotlib.pyplot as plt 
import tkinter as tk 
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg

class GameOfLife(object):

    steps = 0  # recode the steps
    mask = np.ones((3,3)) # a matrix using to calculate the sum of the neighborhood
    mask[1,1] = 0 # itself is zero
    bnd_condition = 'null'

    def __init__(self,cells_shape=(20,20),init_state='random',bnd_condition='null'):
        ''' initialization '''
        # the size of the area
        self.width = cells_shape[0]
        self.height = cells_shape[1]
        # self.extend_width = self.width + 2  
        # self.extend_height = self.width + 2 
        # add two rows and two columns to calculate the neighborhood and boundary 
        self.cells = np.zeros((self.width + 2, self.width + 2))
        self.init_state  = init_state 
        self.gen_init()
        self.draw_cells = 1 - self.cells[1:self.width+1, 1:self.height+1]

    def gen_bnd(self):
        if self.bnd_condition == 'null':
            pass # noting to do 
        elif self.bnd_condition == 'period':
            # periodic boundary condition 
            self.cells[0,0] = self.cells[-2,-2]
            self.cells[0,-1] = self.cells[-2,1]
            self.cells[0,1:-1] = self.cells[-2,1:-1]
            self.cells[-1,1:-1] = self.cells[1,1:-1]
            self.cells[-1,0] = self.cells[1,-2]
            self.cells[-1,-1] = self.cells[1,1]
            self.cells[1:-1,0] = self.cells[1:-1,-2]
            self.cells[1:-1,-1] = self.cells[1:-1,1]

    def gen_init(self):
        self.steps = 0
        if self.init_state == 'random':
            self.cells[1:self.width+1,1:self.height+1] = np.random.randint(2,size=(self.width, self.width))
        elif self.init_state == 'Glider':
            glider = np.zeros((3,3))
            glider[0,1]=glider[1,2]=glider[2,0]=glider[2,1]=glider[2,2]=1
            self.cells[3:6,3:6] = glider
        self.gen_bnd()

    def update_state(self):
        temp_cells = np.zeros((self.width + 2, self.width + 2 ))
        for i in range(1,self.width+1):
            for j in range(1,self.height+1):
                ngb = self.cells[i-1:i+2,j-1:j+2]
                ngb_value = np.sum(ngb*self.mask) # calculate the total live cells in the neighborhood
                if ngb_value == 3:
                    temp_cells[i,j] = 1
                elif ngb_value == 2:
                    temp_cells[i,j] = self.cells[i,j]
                else:
                    temp_cells[i,j] = 0
        self.cells = temp_cells
        self.gen_bnd()
        self.draw_cells = 1 - self.cells[1:self.width+1, 1:self.height+1]
        self.steps += 1

if __name__ == '__main__':
    print('hello')
    game1 = GameOfLife()

    root = tk.Tk()
    root.wm_title("Game of life")

    fig = plt.figure()  #num=np.random.randint(0,100)
    ax1 = fig.add_subplot(111)
    ax1.set_aspect(1.0)
    ax1.set_xlim([0,game1.width])
    ax1.set_ylim([game1.height,0])

    canvas = FigureCanvasTkAgg(fig, master=root)   # A tk.DrawingArea.
    canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=1)
    ax1.pcolor(game1.draw_cells,cmap='hot',edgecolor='k')
    canvas.draw()

    def _convas_draw(game1=game1,steps=10):  # one can change the steps
        for _ in range(steps):
            print(game1.steps)
            #ax1.clear()
            ax1.set_title('Steps: {}'.format(game1.steps))
            ax1.pcolor(game1.draw_cells,cmap='hot',edgecolor='k')
            game1.update_state()
            canvas.draw()
            time.sleep(0.1)

    def _quit():
        root.quit()     # stops mainloop
        root.destroy()  # this is necessary on Windows to prevent Fatal Python Error: PyEval_RestoreThread: NULL tstate
    
    button1 = tk.Button(master=root, text="Draw", command=_convas_draw)
    button2 = tk.Button(master=root, text="Quit", command=_quit)

    button1.pack()
    button2.pack()
    tk.mainloop()